package com.ryan.ohos.extension.nested.component;

import com.ryan.ohos.extension.Gravity;
import com.ryan.ohos.extension.MathUtils;
import com.ryan.ohos.extension.ResourceTable;
import com.ryan.ohos.extension.TextUtils;
import com.ryan.ohos.extension.nested.CollapsingTextHelper;
import com.ryan.ohos.extension.nested.component.coordinatorlayout.ViewOffsetHelper;
import com.ryan.ohos.extension.scroller.animation.AnimationUtils;
import com.ryan.ohos.extension.scroller.animation.Interpolator;
import com.ryan.ohos.extension.scroller.animation.TimeInterpolator;
import com.ryan.ohos.extension.widget.toolbar.Toolbar;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Canvas;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;
import ohos.global.resource.solidxml.Pattern;

import java.io.IOException;
import java.util.HashMap;

/**
 * CollapsingToolbarLayout is a wrapper for {@link Toolbar} which implements a collapsing app bar.
 * It is designed to be used as a direct child of a {@link AppBarLayout}. CollapsingToolbarLayout
 * contains the following features:
 *
 * <h4>Collapsing title</h4>
 * <p>
 * A title which is larger when the layout is fully visible but collapses and becomes smaller as the
 * layout is scrolled off screen. You can set the title to display via {@link
 * #setTitle(String)}. The title appearance can be tweaked via the {@code
 * collapsedTextAppearance} and {@code expandedTextAppearance} attributes.
 *
 * <h4>Content scrim</h4>
 * <p>
 * A full-bleed scrim which is show or hidden when the scroll position has hit a certain threshold.
 * You can change this via {@link #setContentScrim(Element)}.
 *
 * <h4>Parallax scrolling children</h4>
 * <p>
 * Child views can opt to be scrolled within this layout in a parallax fashion. See {@link
 * LayoutParams#COLLAPSE_MODE_PARALLAX} and {@link LayoutParams#setParallaxMultiplier(float)}.
 *
 * <h4>Pinned position children</h4>
 * <p>
 * Child views can opt to be pinned in space globally. This is useful when implementing a collapsing
 * as it allows the {@link Toolbar} to be fixed in place even though this layout is moving. See
 * {@link LayoutParams#COLLAPSE_MODE_PIN}.
 *
 * <p><strong>Do not manually add views to the Toolbar at run time</strong>. We will add a 'dummy
 * view' to the Toolbar which allows us to work out the available space for the title. This can
 * interfere with any views which you add.
 */
public class CollapsingToolbarLayout extends StackLayout
        implements Component.BindStateChangedListener, Component.DrawTask, Component.LayoutRefreshedListener,
        Component.EstimateSizeListener, ComponentContainer.ArrangeListener {

    private static final int DEFAULT_SCRIM_ANIMATION_DURATION = 600;
    private static final HashMap<Integer, Object> TAGS = new HashMap<>();

    private boolean refreshToolbar = true;
    private int toolbarId;
    private Toolbar toolbar;
    private Component toolbarDirectChild;
    private Component dummyView;

    private int expandedMarginStart;
    private int expandedMarginTop;
    private int expandedMarginEnd;
    private int expandedMarginBottom;

    private final Rect tmpRect = new Rect();
    final CollapsingTextHelper collapsingTextHelper;
    private boolean collapsingTitleEnabled;
    private boolean drawCollapsingTitle;

    private Element contentScrim;
    Element statusBarScrim;
    private int scrimAlpha;
    private boolean scrimsAreShown;
    private AnimatorValue scrimAnimator;
    private long scrimAnimationDuration;
    private int scrimVisibleHeightTrigger = -1;
    private boolean isLaidOut;

    private AppBarLayout.OnOffsetChangedListener onOffsetChangedListener;

    int currentOffset;

//    WindowInsetsCompat lastInsets;

    public CollapsingToolbarLayout(Context context) {
        this(context, null);
    }

    public CollapsingToolbarLayout(Context context, AttrSet attrs) {
        this(context, attrs, null);
    }

    public CollapsingToolbarLayout(Context context, AttrSet attrs, String style) {
        super(context, attrs, style);

        setBindStateChangedListener(this);
        addDrawTask(this);
        setLayoutRefreshedListener(this);
        setArrangeListener(this);
        setEstimateSizeListener(this);

        collapsingTextHelper = new CollapsingTextHelper(this);
        collapsingTextHelper.setTextSizeInterpolator(AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR); // AnimationUtils.DECELERATE_INTERPOLATOR);


        try {
            Pattern pattern = context.getResourceManager().getElement(ResourceTable.Pattern_collapsing_toolbar).getPattern();
            collapsingTextHelper.setExpandedTextGravity(Gravity.convert(attrs.getAttr("expandedTitleGravity")
                    .map(Attr::getStringValue).orElse("start|bottom")));

            collapsingTextHelper.setCollapsedTextGravity(Gravity.convert(attrs.getAttr("collapsedTitleGravity")
                            .map(Attr::getStringValue).orElse("start|center_vertical")));

            expandedMarginStart =
                    expandedMarginTop =
                            expandedMarginEnd =
                                    expandedMarginBottom =
                                            attrs.getAttr("expandedTitleMargin")
                                                    .map(Attr::getDimensionValue).orElse((int) pattern.getPatternHash()
                                                    .get("expandedTitleMargin").getFloatValue());

            final int marginStart = attrs.getAttr("expandedTitleMarginStart").map(Attr::getDimensionValue).orElse(-1);
            if (marginStart >= 0) {
                expandedMarginStart = marginStart;
            }

            final int marginEnd = attrs.getAttr("expandedTitleMarginEnd").map(Attr::getDimensionValue).orElse(-1);
            if (marginEnd >= 0) {
                expandedMarginEnd = marginEnd;
            }

            final int marginTop = attrs.getAttr("expandedTitleMarginTop").map(Attr::getDimensionValue).orElse(-1);
            if (marginTop >= 0) {
                expandedMarginTop = marginTop;
            }

            final int marginBottom = attrs.getAttr("expandedTitleMarginBottom").map(Attr::getDimensionValue).orElse(-1);
            if (marginBottom >= 0) {
                expandedMarginBottom = marginBottom;
            }

            collapsingTitleEnabled = attrs.getAttr("titleEnabled").map(Attr::getBoolValue).orElse(true);
            setTitle(attrs.getAttr("title").map(Attr::getStringValue).orElse(""));

            int expandedTitleSize = (int) pattern.getPatternHash().get("expandedTitleSize").getFloatValue();
            int expandedTitleColor = pattern.getPatternHash().get("expandedTitleColor").getColorValue();

            int collapsedTitleSize = (int) pattern.getPatternHash().get("collapsedTitleSize").getFloatValue();
            int collapsedTitleColor = pattern.getPatternHash().get("collapsedTitleColor").getColorValue();

            // First load the default text appearances
            collapsingTextHelper.setExpandedTextAppearance(expandedTitleSize, expandedTitleColor);
            collapsingTextHelper.setCollapsedTextAppearance(collapsedTitleSize, collapsedTitleColor);

            // Now overlay any custom text appearances
            expandedTitleSize = attrs.getAttr("expandedTitleSize").map(Attr::getDimensionValue).orElse(-1);
            if (expandedTitleSize >= 0) {
                collapsingTextHelper.setExpandedTextSize(expandedTitleSize);
            }
            Color expandedColor = attrs.getAttr("expandedTitleColor").map(Attr::getColorValue).orElse(null);
            if (expandedColor != null) {
                collapsingTextHelper.setExpandedTextColor(expandedColor.getValue());
            }

            collapsedTitleSize = attrs.getAttr("collapsedTitleSize").map(Attr::getDimensionValue).orElse(-1);
            if (collapsedTitleSize >= 0) {
                collapsingTextHelper.setCollapsedTextSize(collapsedTitleSize);
            }
            Color collapsedColor = attrs.getAttr("collapsedTitleColor").map(Attr::getColorValue).orElse(null);
            if (collapsedColor != null) {
                collapsingTextHelper.setCollapsedTextColor(collapsedColor.getValue());
            }

            scrimVisibleHeightTrigger = attrs.getAttr("scrimVisibleHeightTrigger").map(Attr::getIntegerValue).orElse(-1);

            collapsingTextHelper.setMaxLines(attrs.getAttr("maxLines").map(Attr::getIntegerValue).orElse(1));

            scrimAnimationDuration = attrs.getAttr("scrimAnimationDuration").map(Attr::getIntegerValue).orElse(DEFAULT_SCRIM_ANIMATION_DURATION);

            setContentScrim(attrs.getAttr("contentScrim").map(Attr::getElement).orElse(null));
            setStatusBarScrim(attrs.getAttr("statusBarScrim").map(Attr::getElement).orElse(null));

            toolbarId = attrs.getAttr("toolbarId").map(Attr::getIntegerValue).orElse(-1);

        } catch (IOException | NotExistException | WrongTypeException e) {
            e.printStackTrace();
        }
//
//        setWillNotDraw(false);
//
//        ViewCompat.setOnApplyWindowInsetsListener(
//                this,
//                new androidx.core.view.OnApplyWindowInsetsListener() {
//                    @Override
//                    public WindowInsetsCompat onApplyWindowInsets(
//                            View v, WindowInsetsCompat insets) {
//                        return onWindowInsetChanged(insets);
//                    }
//                });
    }

    @Override
    public void onComponentBoundToWindow(Component component) {
        final ComponentParent parent = getComponentParent();
        if (parent instanceof AppBarLayout) {
            // Copy over from the ABL whether we should fit system windows
//            ViewCompat.setFitsSystemWindows(this, ViewCompat.getFitsSystemWindows((View) parent));

            if (onOffsetChangedListener == null) {
                onOffsetChangedListener = new OffsetUpdateListener();
            }
            ((AppBarLayout) parent).addOnOffsetChangedListener(onOffsetChangedListener);

            // We're attached, so lets request an inset dispatch
//            ViewCompat.requestApplyInsets(this);
        }
    }

    @Override
    public void onComponentUnboundFromWindow(Component component) {
        final ComponentParent parent = getComponentParent();
        if (onOffsetChangedListener != null && parent instanceof AppBarLayout) {
            ((AppBarLayout) parent).removeOnOffsetChangedListener(onOffsetChangedListener);
        }
        TAGS.clear();
    }

//    @Override
//    protected void onAttachedToWindow() {
//        super.onAttachedToWindow();
//
//        // Add an OnOffsetChangedListener if possible
//        final ViewParent parent = getParent();
//        if (parent instanceof AppBarLayout) {
//            // Copy over from the ABL whether we should fit system windows
//            ViewCompat.setFitsSystemWindows(this, ViewCompat.getFitsSystemWindows((View) parent));
//
//            if (onOffsetChangedListener == null) {
//                onOffsetChangedListener = new OffsetUpdateListener();
//            }
//            ((AppBarLayout) parent).addOnOffsetChangedListener(onOffsetChangedListener);
//
//            // We're attached, so lets request an inset dispatch
//            ViewCompat.requestApplyInsets(this);
//        }
//    }

//    @Override
//    protected void onDetachedFromWindow() {
//        // Remove our OnOffsetChangedListener if possible and it exists
//        final ViewParent parent = getParent();
//        if (onOffsetChangedListener != null && parent instanceof AppBarLayout) {
//            ((AppBarLayout) parent).removeOnOffsetChangedListener(onOffsetChangedListener);
//        }
//
//        super.onDetachedFromWindow();
//    }

//    WindowInsetsCompat onWindowInsetChanged(final WindowInsetsCompat insets) {
//        WindowInsetsCompat newInsets = null;
//
//        if (ViewCompat.getFitsSystemWindows(this)) {
//            // If we're set to fit system windows, keep the insets
//            newInsets = insets;
//        }
//
//        // If our insets have changed, keep them and invalidate the scroll ranges...
//        if (!ObjectsCompat.equals(lastInsets, newInsets)) {
//            lastInsets = newInsets;
//            requestLayout();
//        }
//
//        // Consume the insets. This is done so that child views with fitSystemWindows=true do not
//        // get the default padding functionality from View
//        return insets.consumeSystemWindowInsets();
//    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        // If we don't have a toolbar, the scrim will be not be drawn in drawChild() below.
        // Instead, we draw it here, before our collapsing text.
        ensureToolbar();
        if (contentScrim != null && scrimAlpha > 0) {
            contentScrim.setAlpha(scrimAlpha);
            setBackground(contentScrim);
//            contentScrim.drawToCanvas(canvas);
        }

        // Let the collapsing text helper draw its text
        if (collapsingTitleEnabled && drawCollapsingTitle) {
            collapsingTextHelper.draw(canvas);
        }

        // Now draw the status bar scrim
//        if (statusBarScrim != null && scrimAlpha > 0) {
//            final int topInset = lastInsets != null ? lastInsets.getSystemWindowInsetTop() : 0;
//            if (topInset > 0) {
//                statusBarScrim.setBounds(0, -currentOffset, getWidth(), topInset - currentOffset);
//                statusBarScrim.setAlpha(scrimAlpha);
//                statusBarScrim.drawToCanvas(canvas);
//            }
//        }
    }

//    @Override
//    public void draw(Canvas canvas) {
//        super.draw(canvas);
//
//        // If we don't have a toolbar, the scrim will be not be drawn in drawChild() below.
//        // Instead, we draw it here, before our collapsing text.
//        ensureToolbar();
//        if (toolbar == null && contentScrim != null && scrimAlpha > 0) {
//            contentScrim.mutate().setAlpha(scrimAlpha);
//            contentScrim.draw(canvas);
//        }
//
//        // Let the collapsing text helper draw its text
//        if (collapsingTitleEnabled && drawCollapsingTitle) {
//            collapsingTextHelper.draw(canvas);
//        }
//
//        // Now draw the status bar scrim
//        if (statusBarScrim != null && scrimAlpha > 0) {
//            final int topInset = lastInsets != null ? lastInsets.getSystemWindowInsetTop() : 0;
//            if (topInset > 0) {
//                statusBarScrim.setBounds(0, -currentOffset, getWidth(), topInset - currentOffset);
//                statusBarScrim.mutate().setAlpha(scrimAlpha);
//                statusBarScrim.draw(canvas);
//            }
//        }
//    }

//    @Override
//    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
//        // This is a little weird. Our scrim needs to be behind the Toolbar (if it is present),
//        // but in front of any other children which are behind it. To do this we intercept the
//        // drawChild() call, and draw our scrim just before the Toolbar is drawn
//        boolean invalidated = false;
//        if (contentScrim != null && scrimAlpha > 0 && isToolbarChild(child)) {
//            contentScrim.mutate().setAlpha(scrimAlpha);
//            contentScrim.draw(canvas);
//            invalidated = true;
//        }
//        return super.drawChild(canvas, child, drawingTime) || invalidated;
//    }

    @Override
    public void onRefreshed(Component component) {
        if (contentScrim != null) {
            contentScrim.setBounds(0, 0, component.getWidth(), component.getHeight());
        }
    }

//    @Override
//    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
//        super.onSizeChanged(w, h, oldw, oldh);
//        if (contentScrim != null) {
//            contentScrim.setBounds(0, 0, w, h);
//        }
//    }

    private void ensureToolbar() {
        if (!refreshToolbar) {
            return;
        }

        // First clear out the current Toolbar
        this.toolbar = null;
        toolbarDirectChild = null;

        if (toolbarId != -1) {
            // If we have an ID set, try and find it and it's direct parent to us
            this.toolbar = (Toolbar) findComponentById(toolbarId);
            if (this.toolbar != null) {
                toolbarDirectChild = findDirectChild(this.toolbar);
            }
        }

        if (this.toolbar == null) {
            // If we don't have an ID, or couldn't find a Toolbar with the correct ID, try and find
            // one from our direct children
            Toolbar toolbar = null;
            for (int i = 0, count = getChildCount(); i < count; i++) {
                final Component child = getComponentAt(i);
                if (child instanceof Toolbar) {
                    toolbar = (Toolbar) child;
                    break;
                }
            }
            this.toolbar = toolbar;
        }

        updateDummyView();
        refreshToolbar = false;
    }

    private boolean isToolbarChild(Component child) {
        return (toolbarDirectChild == null || toolbarDirectChild == this)
                ? child == toolbar
                : child == toolbarDirectChild;
    }

    /**
     * Returns the direct child of this layout, which itself is the ancestor of the given view.
     */
    private Component findDirectChild(final Component descendant) {
        Component directChild = descendant;
        for (ComponentParent p = descendant.getComponentParent(); p != this && p != null; p = p.getComponentParent()) {
            if (p instanceof Component) {
                directChild = (Component) p;
            }
        }
        return directChild;
    }

    private void updateDummyView() {
        if (!collapsingTitleEnabled && dummyView != null) {
            // If we have a dummy view and we have our title disabled, remove it from its parent
            final ComponentParent parent = dummyView.getComponentParent();
            if (parent instanceof ComponentContainer) {
                parent.removeComponent(dummyView);
            }
        }
        if (collapsingTitleEnabled && toolbar != null) {
            if (dummyView == null) {
                dummyView = new Component(getContext());
//                ShapeElement shape = new ShapeElement();
//                shape.setRgbColor(new RgbColor(100, 100, 100));
//                dummyView.setBackground(shape);
            }
            if (dummyView.getComponentParent() == null) {
                toolbar.addComponent(dummyView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            }
        }
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        ensureToolbar();
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//
//        final int mode = EstimateSpec.getMode(heightMeasureSpec);
//        final int topInset = lastInsets != null ? lastInsets.getSystemWindowInsetTop() : 0;
//        if (mode == EstimateSpec.UNCONSTRAINT && topInset > 0) {
//            // If we have a top inset and we're set to wrap_content height we need to make sure
//            // we add the top inset to our height, therefore we re-measure
//            heightMeasureSpec =
//                    EstimateHelper.makeEstimateSpec(getEstimatedHeight() + topInset, EstimateSpec.PRECISE);
//            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//        }

        return false;
    }

    @Override
    public boolean onArrange(int left, int top, int width, int height) {
//        if (lastInsets != null) {
//            // Shift down any views which are not set to fit system windows
//            final int insetTop = lastInsets.getSystemWindowInsetTop();
//            for (int i = 0, z = getChildCount(); i < z; i++) {
//                final View child = getChildAt(i);
//                if (!ViewCompat.getFitsSystemWindows(child)) {
//                    if (child.getTop() < insetTop) {
//                        // If the child isn't set to fit system windows but is drawing within
//                        // the inset offset it down
//                        ViewCompat.offsetTopAndBottom(child, insetTop);
//                    }
//                }
//            }
//        }

        // Update our child view offset helpers so that they track the correct layout coordinates
        for (int i = 0, z = getChildCount(); i < z; i++) {
            getViewOffsetHelper(getComponentAt(i)).onViewLayout();
        }

        // Update the collapsed bounds by getting its transformed bounds
        if (collapsingTitleEnabled && dummyView != null) {
            // We only draw the title if the dummy view is being displayed (Toolbar removes
            // views if there is no space)
            drawCollapsingTitle =
                    dummyView.isBoundToWindow() && dummyView.getVisibility() == VISIBLE;

            if (drawCollapsingTitle) {
                final boolean isRtl =
                        getLayoutDirection() == LayoutDirection.RTL;

                // Update the collapsed bounds
                final int maxOffset =
                        getMaxOffsetForPinChild(toolbarDirectChild != null ? toolbarDirectChild : toolbar);
//                DescendantOffsetUtils.getDescendantRect(this, dummyView, tmpRect);
                Rect dummy = dummyView.getComponentPosition();
                tmpRect.set(dummy.left, dummy.top, dummy.right, dummy.bottom);
                collapsingTextHelper.setCollapsedBounds(
                        tmpRect.left + (isRtl ? toolbar.getTitleMarginEnd() : toolbar.getTitleMarginStart()),
                        tmpRect.top + maxOffset + toolbar.getTitleMarginTop(),
                        tmpRect.right - (isRtl ? toolbar.getTitleMarginStart() : toolbar.getTitleMarginEnd()),
                        tmpRect.bottom + maxOffset - toolbar.getTitleMarginBottom());

                // Update the expanded bounds
                collapsingTextHelper.setExpandedBounds(
                        isRtl ? expandedMarginEnd : expandedMarginStart,
                        tmpRect.top + expandedMarginTop,
                        width - (isRtl ? expandedMarginStart : expandedMarginEnd),
                        height - expandedMarginBottom);
                // Now recalculate using the new bounds
                collapsingTextHelper.recalculate();
            }
        }

        // Set our minimum height to enable proper AppBarLayout collapsing
        if (toolbar != null) {
            if (collapsingTitleEnabled && TextUtils.isEmpty(collapsingTextHelper.getText())) {
                // If we do not currently have a title, try and grab it from the Toolbar
                setTitle(toolbar.getTitle());
            }
            if (toolbarDirectChild == null || toolbarDirectChild == this) {
                setMinHeight(getHeightWithMargins(toolbar));
            } else {
                setMinHeight(getHeightWithMargins(toolbarDirectChild));
            }
        }

        updateScrimVisibility();

        // Apply any view offsets, this should be done at the very end of layout
        for (int i = 0, z = getChildCount(); i < z; i++) {
            getViewOffsetHelper(getComponentAt(i)).applyOffsets();
        }

        isLaidOut = true;
        return false;
    }

    private static int getHeightWithMargins(final Component view) {
        final ComponentContainer.LayoutConfig lp = view.getLayoutConfig();
        return view.getHeight() + lp.getMarginTop() + lp.getMarginBottom();
    }


    static ViewOffsetHelper getViewOffsetHelper(Component view) {
        ViewOffsetHelper offsetHelper = (ViewOffsetHelper) TAGS.get(view.hashCode());
        if (offsetHelper == null) {
            offsetHelper = new ViewOffsetHelper(view);
            TAGS.put(view.hashCode(), offsetHelper);
        }
        return offsetHelper;
    }

    /**
     * Sets the title to be displayed by this view, if enabled.
     *
     * @attr ref R.styleable#CollapsingToolbarLayout_title
     * @see #setTitleEnabled(boolean)
     * @see #getTitle()
     */
    public void setTitle(String title) {
        collapsingTextHelper.setText(title);
        updateContentDescriptionFromTitle();
    }

    /**
     * Returns the title currently being displayed by this view. If the title is not enabled, then
     * this will return {@code null}.
     *
     * @attr ref R.styleable#CollapsingToolbarLayout_title
     */
    public String getTitle() {
        return collapsingTitleEnabled ? collapsingTextHelper.getText() : null;
    }

    /**
     * Sets whether this view should display its own title.
     *
     * <p>The title displayed by this view will shrink and grow based on the scroll offset.
     *
     * @attr ref R.styleable#CollapsingToolbarLayout_titleEnabled
     * @see #setTitle(String)
     * @see #isTitleEnabled()
     */
    public void setTitleEnabled(boolean enabled) {
        if (enabled != collapsingTitleEnabled) {
            collapsingTitleEnabled = enabled;
            updateContentDescriptionFromTitle();
            updateDummyView();
            postLayout();
        }
    }

    /**
     * Returns whether this view is currently displaying its own title.
     *
     * @attr ref R.styleable#CollapsingToolbarLayout_titleEnabled
     * @see #setTitleEnabled(boolean)
     */
    public boolean isTitleEnabled() {
        return collapsingTitleEnabled;
    }

    /**
     * Set whether the content scrim and/or status bar scrim should be shown or not. Any change in the
     * vertical scroll may overwrite this value. Any visibility change will be animated if this view
     * has already been laid out.
     *
     * @param shown whether the scrims should be shown
     * @see #getStatusBarScrim()
     * @see #getContentScrim()
     */
    public void setScrimsShown(boolean shown) {
        setScrimsShown(shown, isLaidOut); // ViewCompat.isLaidOut(this) && !isInEditMode());
    }

    /**
     * Set whether the content scrim and/or status bar scrim should be shown or not. Any change in the
     * vertical scroll may overwrite this value.
     *
     * @param shown   whether the scrims should be shown
     * @param animate whether to animate the visibility change
     * @see #getStatusBarScrim()
     * @see #getContentScrim()
     */
    public void setScrimsShown(boolean shown, boolean animate) {
        if (scrimsAreShown != shown) {
            if (animate) {
                animateScrim(shown ? 0xFF : 0x0);
            } else {
                setScrimAlpha(shown ? 0xFF : 0x0);
            }
            scrimsAreShown = shown;
        }
    }

    private void animateScrim(int targetAlpha) {
        ensureToolbar();
        if (scrimAnimator == null) {
            scrimAnimator = new AnimatorValue();
            scrimAnimator.setDuration(scrimAnimationDuration);
        } else if (scrimAnimator.isRunning()) {
            scrimAnimator.cancel();
        }


        TimeInterpolator interpolator = targetAlpha > scrimAlpha
                ? AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR
                : AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR;
//            scrimAnimator.setInterpolator(
//                    targetAlpha > scrimAlpha
//                            ? AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR
//                            : AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
        int current = scrimAlpha;
        scrimAnimator.setValueUpdateListener((animatorValue, fraction) -> {
            setScrimAlpha(current + (int) ((targetAlpha - current) * interpolator.getInterpolation(fraction)));
        });

//        scrimAnimator.setIntValues(scrimAlpha, targetAlpha);
        scrimAnimator.start();
    }

    void setScrimAlpha(int alpha) {
        if (alpha != scrimAlpha) {
            final Element contentScrim = this.contentScrim;
            if (contentScrim != null && toolbar != null) {
                toolbar.invalidate();
            }
            scrimAlpha = alpha;
            invalidate();
        }
    }

    int getScrimAlpha() {
        return scrimAlpha;
    }

    /**
     * Set the drawable to use for the content scrim from resources. Providing null will disable the
     * scrim functionality.
     *
     * @param drawable the drawable to display
     */
    public void setContentScrim(Element drawable) {
        if (contentScrim != drawable) {
            if (contentScrim != null) {
                contentScrim.setCallback(null);
            }
            contentScrim = drawable;
            if (contentScrim != null) {
                contentScrim.setBounds(0, 0, getWidth(), getHeight());
                // TODO drawable callback
//                contentScrim.setCallback(this);
                contentScrim.setAlpha(scrimAlpha);
            }
            invalidate();
        }
    }

    /**
     * Set the color to use for the content scrim.
     *
     * @param color the color to display
     * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
     * @see #getContentScrim()
     */
    public void setContentScrimColor(int color) {
        ShapeElement element = new ShapeElement();
        element.setRgbColor(new RgbColor(color));
        setContentScrim(element);
    }

    /**
     * Set the drawable to use for the content scrim from resources.
     *
     * @param resId drawable resource id
     * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
     * @see #getContentScrim()
     */
    public void setContentScrimResource(int resId) {
        setContentScrim(new ShapeElement(getContext(), resId));
    }

    /**
     * Returns the drawable which is used for the foreground scrim.
     *
     * @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
     */

    public Element getContentScrim() {
        return contentScrim;
    }

    /**
     * Set the drawable to use for the status bar scrim from resources. Providing null will disable
     * the scrim functionality.
     *
     * <p>This scrim is only shown when we have been given a top system inset.
     *
     * @param drawable the drawable to display
     * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
     * @see #getStatusBarScrim()
     */
    public void setStatusBarScrim(Element drawable) {
        if (statusBarScrim != drawable) {
            if (statusBarScrim != null) {
                statusBarScrim.setCallback(null);
            }
            statusBarScrim = drawable;
            if (statusBarScrim != null) {
//                if (statusBarScrim.isStateful()) {
//                    statusBarScrim.setState(getDrawableState());
//                }
//                DrawableCompat.setLayoutDirection(statusBarScrim, ViewCompat.getLayoutDirection(this));
                statusBarScrim.setVisible(getVisibility() == VISIBLE, false);
                // TODO drawable callback
//                statusBarScrim.setCallback(this);
                statusBarScrim.setAlpha(scrimAlpha);
            }
            invalidate();
        }
    }

//    @Override
//    protected void drawableStateChanged() {
//        super.drawableStateChanged();
//
//        final int[] state = getDrawableState();
//        boolean changed = false;
//
//        Drawable d = statusBarScrim;
//        if (d != null && d.isStateful()) {
//            changed |= d.setState(state);
//        }
//        d = contentScrim;
//        if (d != null && d.isStateful()) {
//            changed |= d.setState(state);
//        }
//        if (collapsingTextHelper != null) {
//            changed |= collapsingTextHelper.setState(state);
//        }
//
//        if (changed) {
//            invalidate();
//        }
//    }

//    @Override
//    protected boolean verifyDrawable(Drawable who) {
//        return super.verifyDrawable(who) || who == contentScrim || who == statusBarScrim;
//    }

    @Override
    public void setVisibility(int visibility) {
        super.setVisibility(visibility);

        final boolean visible = visibility == VISIBLE;
        if (statusBarScrim != null && statusBarScrim.getVisible() != visible) {
            statusBarScrim.setVisible(visible, false);
        }
        if (contentScrim != null && contentScrim.getVisible() != visible) {
            contentScrim.setVisible(visible, false);
        }
    }

    /**
     * Set the color to use for the status bar scrim.
     *
     * <p>This scrim is only shown when we have been given a top system inset.
     *
     * @param color the color to display
     * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
     * @see #getStatusBarScrim()
     */
    public void setStatusBarScrimColor(int color) {
        ShapeElement element = new ShapeElement();
        element.setRgbColor(new RgbColor(color));
        setStatusBarScrim(element);
    }

    /**
     * Set the drawable to use for the status bar scrim from resources.
     *
     * @param resId drawable resource id
     * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
     * @see #getStatusBarScrim()
     */
    public void setStatusBarScrimResource(int resId) {
        setStatusBarScrim(new ShapeElement(getContext(), resId));
    }

    /**
     * Returns the drawable which is used for the status bar scrim.
     *
     * @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
     */

    public Element getStatusBarScrim() {
        return statusBarScrim;
    }

//    /**
//     * Sets the text color and size for the collapsed title from the specified TextAppearance
//     * resource.
//     *
//     * @attr ref
//     *     com.google.android.material.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance
//     */
//    public void setCollapsedTitleTextAppearance(intint resId) {
//        collapsingTextHelper.setCollapsedTextAppearance(resId);
//    }

    /**
     * Sets the text color of the collapsed title.
     *
     * @param color The new text color in ARGB format
     */
    public void setCollapsedTitleTextColor(int color) {
        collapsingTextHelper.setCollapsedTextColor(color);
    }

//    /**
//     * Sets the text colors of the collapsed title.
//     *
//     * @param colors ColorStateList containing the new text colors
//     */
//    public void setCollapsedTitleTextColor(int colors) {
//        collapsingTextHelper.setCollapsedTextColor(colors);
//    }

    /**
     * Sets the horizontal alignment of the collapsed title and the vertical gravity that will be used
     * when there is extra space in the collapsed bounds beyond what is required for the title itself.
     *
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity
     */
    public void setCollapsedTitleGravity(int gravity) {
        collapsingTextHelper.setCollapsedTextGravity(gravity);
    }

    /**
     * Returns the horizontal and vertical alignment for title when collapsed.
     *
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_collapsedTitleGravity
     */
    public int getCollapsedTitleGravity() {
        return collapsingTextHelper.getCollapsedTextGravity();
    }

//    /**
//     * Sets the text color and size for the expanded title from the specified TextAppearance resource.
//     *
//     * @attr ref
//     *     com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance
//     */
//    public void setExpandedTitleTextAppearance(int resId) {
//        collapsingTextHelper.setExpandedTextAppearance(resId);
//    }

    /**
     * Sets the text color of the expanded title.
     *
     * @param color The new text color in ARGB format
     */
    public void setExpandedTitleColor(int color) {
        collapsingTextHelper.setExpandedTextColor(color);
    }

//    /**
//     * Sets the text colors of the expanded title.
//     *
//     * @param colors ColorStateList containing the new text colors
//     */
//    public void setExpandedTitleTextColor(ColorStateList colors) {
//        collapsingTextHelper.setExpandedTextColor(colors);
//    }

    /**
     * Sets the horizontal alignment of the expanded title and the vertical gravity that will be used
     * when there is extra space in the expanded bounds beyond what is required for the title itself.
     *
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleGravity
     */
    public void setExpandedTitleGravity(int gravity) {
        collapsingTextHelper.setExpandedTextGravity(gravity);
    }

    /**
     * Returns the horizontal and vertical alignment for title when expanded.
     *
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleGravity
     */
    public int getExpandedTitleGravity() {
        return collapsingTextHelper.getExpandedTextGravity();
    }

    /**
     * Set the typeface to use for the collapsed title.
     *
     * @param typeface typeface to use, or {@code null} to use the default.
     */
    public void setCollapsedTitleTypeface(Font typeface) {
        collapsingTextHelper.setCollapsedTypeface(typeface);
    }

    /**
     * Returns the typeface used for the collapsed title.
     */

    public Font getCollapsedTitleTypeface() {
        return collapsingTextHelper.getCollapsedTypeface();
    }

    /**
     * Set the typeface to use for the expanded title.
     *
     * @param typeface typeface to use, or {@code null} to use the default.
     */
    public void setExpandedTitleTypeface(Font typeface) {
        collapsingTextHelper.setExpandedTypeface(typeface);
    }

    /**
     * Returns the typeface used for the expanded title.
     */

    public Font getExpandedTitleTypeface() {
        return collapsingTextHelper.getExpandedTypeface();
    }

    /**
     * Sets the expanded title margins.
     *
     * @param start  the starting title margin in pixels
     * @param top    the top title margin in pixels
     * @param end    the ending title margin in pixels
     * @param bottom the bottom title margin in pixels
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMargin
     * @see #getExpandedTitleMarginStart()
     * @see #getExpandedTitleMarginTop()
     * @see #getExpandedTitleMarginEnd()
     * @see #getExpandedTitleMarginBottom()
     */
    public void setExpandedTitleMargin(int start, int top, int end, int bottom) {
        expandedMarginStart = start;
        expandedMarginTop = top;
        expandedMarginEnd = end;
        expandedMarginBottom = bottom;
        postLayout();
    }

    /**
     * @return the starting expanded title margin in pixels
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart
     * @see #setExpandedTitleMarginStart(int)
     */
    public int getExpandedTitleMarginStart() {
        return expandedMarginStart;
    }

    /**
     * Sets the starting expanded title margin in pixels.
     *
     * @param margin the starting title margin in pixels
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginStart
     * @see #getExpandedTitleMarginStart()
     */
    public void setExpandedTitleMarginStart(int margin) {
        expandedMarginStart = margin;
        postLayout();
    }

    /**
     * @return the top expanded title margin in pixels
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop
     * @see #setExpandedTitleMarginTop(int)
     */
    public int getExpandedTitleMarginTop() {
        return expandedMarginTop;
    }

    /**
     * Sets the top expanded title margin in pixels.
     *
     * @param margin the top title margin in pixels
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginTop
     * @see #getExpandedTitleMarginTop()
     */
    public void setExpandedTitleMarginTop(int margin) {
        expandedMarginTop = margin;
        postLayout();
    }

    /**
     * @return the ending expanded title margin in pixels
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
     * @see #setExpandedTitleMarginEnd(int)
     */
    public int getExpandedTitleMarginEnd() {
        return expandedMarginEnd;
    }

    /**
     * Sets the ending expanded title margin in pixels.
     *
     * @param margin the ending title margin in pixels
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginEnd
     * @see #getExpandedTitleMarginEnd()
     */
    public void setExpandedTitleMarginEnd(int margin) {
        expandedMarginEnd = margin;
        postLayout();
    }

    /**
     * @return the bottom expanded title margin in pixels
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
     * @see #setExpandedTitleMarginBottom(int)
     */
    public int getExpandedTitleMarginBottom() {
        return expandedMarginBottom;
    }

    /**
     * Sets the bottom expanded title margin in pixels.
     *
     * @param margin the bottom title margin in pixels
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_expandedTitleMarginBottom
     * @see #getExpandedTitleMarginBottom()
     */
    public void setExpandedTitleMarginBottom(int margin) {
        expandedMarginBottom = margin;
        postLayout();
    }

    /**
     * Sets the maximum number of lines to display in the expanded state.
     * Experimental Feature.
     */
    public void setMaxLines(int maxLines) {
        collapsingTextHelper.setMaxLines(maxLines);
    }

    /**
     * Gets the maximum number of lines to display in the expanded state.
     * Experimental Feature.
     */
    public int getMaxLines() {
        return collapsingTextHelper.getMaxLines();
    }

    /**
     * Set the amount of visible height in pixels used to define when to trigger a scrim visibility
     * change.
     *
     * <p>If the visible height of this view is less than the given value, the scrims will be made
     * visible, otherwise they are hidden.
     *
     * @param height value in pixels used to define when to trigger a scrim visibility change
     * @attr ref
     * com.google.android.material.R.styleable#CollapsingToolbarLayout_scrimVisibleHeightTrigger
     */
    public void setScrimVisibleHeightTrigger(final int height) {
        if (scrimVisibleHeightTrigger != height) {
            scrimVisibleHeightTrigger = height;
            // Update the scrim visibility
            updateScrimVisibility();
        }
    }

    /**
     * Returns the amount of visible height in pixels used to define when to trigger a scrim
     * visibility change.
     *
     * @see #setScrimVisibleHeightTrigger(int)
     */
    public int getScrimVisibleHeightTrigger() {
        if (scrimVisibleHeightTrigger >= 0) {
            // If we have one explicitly set, return it
            return scrimVisibleHeightTrigger;
        }

        // Otherwise we'll use the default computed value
        final int insetTop = toolbar.getStatusBarHeight(); // lastInsets != null ? lastInsets.getSystemWindowInsetTop() : 0;

        final int minHeight = getMinHeight();
        if (minHeight > 0) {
            // If we have a minHeight set, lets use 2 * minHeight (capped at our height)
            return Math.min((minHeight * 2) + insetTop, getHeight());
        }

        // If we reach here then we don't have a min height set. Instead we'll take a
        // guess at 1/3 of our height being visible
        return getHeight() / 3;
    }

    /**
     * Set the duration used for scrim visibility animations.
     *
     * @param duration the duration to use in milliseconds
     * @attr ref com.google.android.material.R.styleable#CollapsingToolbarLayout_scrimAnimationDuration
     */
    public void setScrimAnimationDuration(final long duration) {
        scrimAnimationDuration = duration;
    }

    /**
     * Returns the duration in milliseconds used for scrim visibility animations.
     */
    public long getScrimAnimationDuration() {
        return scrimAnimationDuration;
    }


//    @Override
//    protected boolean checkLayoutParams(ComponentContainer.LayoutConfig p) {
//        return p instanceof LayoutParams;
//    }


    @Override
    public ComponentContainer.LayoutConfig verifyLayoutConfig(ComponentContainer.LayoutConfig config) {
        if (config instanceof LayoutParams) return config;
        return new LayoutParams(config);
    }

    @Override
    public ComponentContainer.LayoutConfig createLayoutConfig(Context context, AttrSet attrSet) {
        return new LayoutParams(getContext(), attrSet);
    }

    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

//    @Override
//    public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
//        return new LayoutParams(getContext(), attrs);
//    }

//    @Override
//    protected FrameLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
//        return new LayoutParams(p);
//    }

    public static class LayoutParams extends LayoutConfig {

        private static final float DEFAULT_PARALLAX_MULTIPLIER = 0.5f;

        /**
         * The view will act as normal with no collapsing behavior.
         */
        public static final int COLLAPSE_MODE_OFF = 0;

        /**
         * The view will pin in place until it reaches the bottom of the {@link
         * CollapsingToolbarLayout}.
         */
        public static final int COLLAPSE_MODE_PIN = 1;

        /**
         * The view will scroll in a parallax fashion. See {@link #setParallaxMultiplier(float)} to
         * change the multiplier used.
         */
        public static final int COLLAPSE_MODE_PARALLAX = 2;

        int collapseMode = COLLAPSE_MODE_OFF;
        float parallaxMult = DEFAULT_PARALLAX_MULTIPLIER;

        public LayoutParams(Context c, AttrSet attrs) {
            super(c, attrs);

//            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CollapsingToolbarLayout_Layout);
            collapseMode = getMode(attrs.getAttr("layout_collapseMode").map(Attr::getStringValue).orElse(""));
            setParallaxMultiplier(attrs.getAttr("layout_collapseParallaxMultiplier").map(Attr::getFloatValue)
                    .orElse(DEFAULT_PARALLAX_MULTIPLIER));
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(int width, int height, int gravity) {
            super(width, height, gravity);
        }

        public LayoutParams(ComponentContainer.LayoutConfig p) {
            super(p);
        }

        public LayoutParams(LayoutConfig source) {
            super(source);
        }

//        public LayoutParams(FrameLayout.LayoutParams source) {
//            // The copy constructor called here only exists on API 19+.
//            super(source);
//        }

        /**
         * Set the collapse mode.
         *
         * @param collapseMode one of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN} or {@link
         *                     #COLLAPSE_MODE_PARALLAX}.
         */
        public void setCollapseMode(int collapseMode) {
            this.collapseMode = collapseMode;
        }

        /**
         * Returns the requested collapse mode.
         *
         * @return the current mode. One of {@link #COLLAPSE_MODE_OFF}, {@link #COLLAPSE_MODE_PIN} or
         * {@link #COLLAPSE_MODE_PARALLAX}.
         */
        public int getCollapseMode() {
            return collapseMode;
        }

        /**
         * Set the parallax scroll multiplier used in conjunction with {@link #COLLAPSE_MODE_PARALLAX}.
         * A value of {@code 0.0} indicates no movement at all, {@code 1.0f} indicates normal scroll
         * movement.
         *
         * @param multiplier the multiplier.
         * @see #getParallaxMultiplier()
         */
        public void setParallaxMultiplier(float multiplier) {
            parallaxMult = multiplier;
        }

        /**
         * Returns the parallax scroll multiplier used in conjunction with {@link
         * #COLLAPSE_MODE_PARALLAX}.
         *
         * @see #setParallaxMultiplier(float)
         */
        public float getParallaxMultiplier() {
            return parallaxMult;
        }

        private int getMode(String mode) {
            switch (mode) {
                case "pin":
                    return COLLAPSE_MODE_PIN;

                case "parallax":
                    return COLLAPSE_MODE_PARALLAX;

                default:
                    return COLLAPSE_MODE_OFF;
            }
        }
    }

    /**
     * Show or hide the scrims if needed
     */
    final void updateScrimVisibility() {
        if (contentScrim != null || statusBarScrim != null) {
            setScrimsShown(getHeight() + currentOffset < getScrimVisibleHeightTrigger());
        }
    }

    final int getMaxOffsetForPinChild(Component child) {
        final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);
        final LayoutParams lp = (LayoutParams) child.getLayoutConfig();
        return getHeight() - offsetHelper.getLayoutTop() - child.getHeight() - lp.getMarginBottom();
    }

    private void updateContentDescriptionFromTitle() {
        // Set this layout's contentDescription to match the title if it's shown by CollapsingTextHelper
//        setContentDescription(getTitle());
    }

    private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener {
        OffsetUpdateListener() {
        }

        @Override
        public void onOffsetChanged(AppBarLayout layout, int verticalOffset) {
            currentOffset = verticalOffset;

            final int insetTop = toolbar.getStatusBarHeight(); // lastInsets != null ? lastInsets.getSystemWindowInsetTop() : 0;

            for (int i = 0, z = getChildCount(); i < z; i++) {
                final Component child = getComponentAt(i);
                final LayoutParams lp = (LayoutParams) child.getLayoutConfig();
                final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);

                switch (lp.collapseMode) {
                    case LayoutParams.COLLAPSE_MODE_PIN:
                        offsetHelper.setTopAndBottomOffset(
                                MathUtils.clamp(-verticalOffset, 0, getMaxOffsetForPinChild(child)));
                        break;
                    case LayoutParams.COLLAPSE_MODE_PARALLAX:
                        offsetHelper.setTopAndBottomOffset(Math.round(-verticalOffset * lp.parallaxMult));
                        break;
                    default:
                        break;
                }
            }

            // Show or hide the scrims if needed
            updateScrimVisibility();

//            if (statusBarScrim != null && insetTop > 0) {
//                ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this);
//            }

            // Update the collapsing text's fraction
            final int expandRange =
                    getHeight() - getMinHeight() - insetTop;
            collapsingTextHelper.setExpansionFraction(Math.abs(verticalOffset) / (float) expandRange);
        }
    }
}
